using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace WeBlog.DataContext
{
    public class InMemoryDbSet<T> : IDbSet<T> where T : class
    {

        readonly HashSet<T> _set;
        readonly IQueryable<T> _queryableSet;


        public InMemoryDbSet() : this(Enumerable.Empty<T>()) { }

        public InMemoryDbSet(IEnumerable<T> entities)
        {
            _set = new HashSet<T>();

            foreach (var entity in entities)
            {
                _set.Add(entity);
            }

            _queryableSet = _set.AsQueryable();
        }

        public T Add(T entity)
        {
            _set.Add(entity);
            return entity;

        }

        public T Attach(T entity)
        {
            _set.Add(entity);
            return entity;
        }

        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
        {
            return Activator.CreateInstance<TDerivedEntity>();
        }

        public T Create()
        {
            return Activator.CreateInstance<T>();
        }

        public T Find(params object[] keyValues)
        {
            //This collection is used to override how the find method works, this is useful when 
            //you have composite keys or some type of unusual primary key defined
            var overrides = new Dictionary<Type, Func<dynamic, bool>>();

            if (overrides.ContainsKey(typeof(T)))
                return _queryableSet.SingleOrDefault(overrides[typeof(T)]);

            //Try to dynamically build the Func using a naming convention for the keyName. 
            //Typically this is object name and Id so for a Person class it will be "PersonId"
            var conventionalKeyName = typeof(T).Name + "Id";
            if (typeof(T).GetProperties().ToList().Any(x => x.Name.Equals(conventionalKeyName, StringComparison.InvariantCultureIgnoreCase))) {
                var p = Expression.Parameter(typeof(T), "x");
                var baseExpression = Expression.Equal(
                    Expression.Property(p, conventionalKeyName),
                    Expression.Constant(keyValues.First())
                    );
                var predicate = Expression.Lambda<Func<T, bool>>(baseExpression, p);
                return _queryableSet.SingleOrDefault(predicate);
            }

            throw new NotImplementedException(String.Format("Find method failed for type {0}", typeof(T).Name));
        }

        public System.Collections.ObjectModel.ObservableCollection<T> Local
        {
            get {
                var collection = new System.Collections.ObjectModel.ObservableCollection<T>();
                _queryableSet.ToList().ForEach(collection.Add);
                return collection;
            }
        }

        public T Remove(T entity)
        {
            _set.Remove(entity);
            return entity;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return _set.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public Type ElementType
        {
            get { return _queryableSet.ElementType; }
        }

        public System.Linq.Expressions.Expression Expression
        {
            get { return _queryableSet.Expression; }
        }

        public IQueryProvider Provider
        {
            get { return _queryableSet.Provider; }
        }
    }
}